package EA.testproblems;
import EA.*;
import RKUjava.math.RKUFunction;
import RKUjava.lang.RKUMath;
import java.util.Hashtable;
import RKUjava.util.RKUStringUtils;

/**
   This testcase generator was presented at CEC1999. It can be used to generate
   dynamic fitnesslandscapes with an arbitrary number of peaks. The peaks
   can move, change height and width. The generated problems are not hard
   in the sense that the generated landscapes are both smooth, continous
   and without random noise of any kind.<br><br>

   <table border="0" cellpadding="2" cellspacing="0">
   <tr bgcolor="#a0a0a0">
   <td colspan="2" valign="top"><b>Problem description</b></td>
   </tr>
   <tr bgcolor="#e0e0e0">
   <td valign="top" width="200"><b>Name:</b></td>
   <td valign="top">De Jong TCG 1</td>
   </tr>
   <tr bgcolor="#e0e0e0">
   <td valign="top"><b>Nickname:</b></td>
   <td valign="top">&nbsp;</td>
   </tr>
   <tr bgcolor="#e0e0e0">
   <td valign="top"><b>Intended usage:</b></td>
   <td valign="top">Initial tests of algorithms for dynamic problems.</td>
   </tr>

   <tr>
   <td colspan="2" valign="top">&nbsp;</td>
   </tr>
   <tr bgcolor="#a0a0a0">
   <td colspan="2" valign="top"><b>Problem details</b></td>
   </tr>
   <tr bgcolor="#e0e0e0">
   <td valign="top"><b>Function:</b></td>
   <td valign="top">A peak consist of a position, a height and a width. When
   the fitness is calculated the position is evaluated with respect to each
   of the peaks. The assigned fitness is the maximum of <br>
   <center>fitness = peak_height - (dist(pos, peak_pos)/peak_width)</center><br>
   If this value is below zero then the fitness is set to be zero.
   </td>
   </tr>
   <tr bgcolor="#e0e0e0">
   <td valign="top"><b>Plots:</b></td>
   <td valign="top"><img src="../../images/testproblems/dynamicdejongtcg1.gif">
   </td>
   </tr>
   <tr bgcolor="#e0e0e0">
   <td valign="top"><b>Ranges:</b></td>
   <td valign="top">Can be set in inputfile. </td>
   </tr>
   <tr bgcolor="#e0e0e0">
   <td valign="top"><b>Type:</b></td>
   <td valign="top">Maximization</td>
   </tr>
   <tr bgcolor="#e0e0e0">
   <td valign="top"><b>No. of maximas:</b></td>
   <td valign="top">Variable</td>
   </tr>
   <tr bgcolor="#e0e0e0">
   <td valign="top"><b>No. of minimas:</b></td>
   <td valign="top">0 (But controlled by the maximas)</td>
   </tr>
   <tr bgcolor="#e0e0e0">
   <td valign="top"><b>Optima radius:</b></td>
   <td valign="top">0.2</td>
   </tr>
   <tr bgcolor="#e0e0e0">
   <td valign="top"><b>Optima descriptions:</b></td>
   <td valign="top">A peak has a conelike shape with a single optimal point. It
   is symmetric around its maxima.</td>
   </tr>
   </table>

*/
public class DynamicDeJongTCG1 extends DynamicNumericalProblem
{
    
  RKUFunction[][] initialpeaks;
  double[][] momentumonpeaks;

  double[] peakwidths;

  double[] curparams = new double[3];

  RKUFunction staticperiodfunction;

  // ****************** Optimization variables *******************
  double[] tmppos = new double[2];
  
  public DynamicDeJongTCG1()
    {
      super();

      double[] optimas;

      name = "Dynamic De Jong TCG1";
      objectivefunction = new NumericalFitness(this, null){
	int i;
	DynamicDeJongTCG1 tcg;
	//	double bestvalue;
	double curdistance, curpeakfitness, curfitness;

	public void initialize(Object container1, Object container2)
	{
	  tcg = (DynamicDeJongTCG1)container1;
	};

	public double Fitness_calcFitness_inner(double[] realpos)
	{
	  double bestvalue = 0;

	  for (i=0;i<tcg.knownmaxima.length;i++) { 
	    curdistance = tcg.knownmaxima[i].distanceToOptimum(realpos);
	    curpeakfitness =  tcg.knownmaxima[i].getFitness();
	    if (tcg.peakwidths[i]==0) {
	      if (curdistance==0 && bestvalue<curpeakfitness) {
		bestvalue = curpeakfitness;
	      }
	    }
	    else {
	      curfitness = curpeakfitness - curdistance/tcg.peakwidths[i];
	      if (bestvalue<curfitness) {
		bestvalue = curfitness;
	      }
	    }
	  }

	  return bestvalue;
	};
      };

      dimensions = 2;
      ismaximization = true;
      optimumradius = 0.2;

      intervals = new Interval[2];
      for (int i=0;i<2;i++) {
	intervals[i] = new Interval(-10,10);
      };
      knownminima = new NumericalOptimum[0];
    }

  /** Reset the dynamic problem. This will set curiteration to zero. 
      You should call this every time you restart your algorithm.
  */
  public void DNP_resetProblem_inner()
    {
      curiteration_cnt = 0;
      staticperiod = 0;
      staticperiod_cnt = 0;
      curparams[0] = 0;
      curparams[1] = 0;
      curparams[2] = 0;
      updateProblem();
    }

  /** Update the dynamic problem. You are responsible for calling this 
      method from your main loop. 
      @param curiteration The current iteration in your algorithm.
      @return True if the problem was updated.
  */
  public boolean updateProblem()
    {
      double[] oldpos;
      double oldfit, oldwidth;
      int i, j;
      boolean updated = false;

      if (curiteration_cnt >= staticperiod-1) {

	staticperiod = (int)Math.rint(staticperiodfunction.evaluate(curparams));
	curparams[0] = staticperiod_cnt;
	curparams[1] = staticperiod;
	curparams[2] = curiteration;

	for (i=0;i<knownmaxima.length;i++) {
	  oldpos = knownmaxima[i].getPos();
	  oldfit = knownmaxima[i].getFitness();
	  for (j=0;j<dimensions;j++) {
	    tmppos[j] = oldpos[j]*momentumonpeaks[i][j] + (1-momentumonpeaks[i][j])*RKUMath.bringInClosedRange(initialpeaks[i][j].evaluate(curparams), intervals[j].min, intervals[j].max);
	  }
	  knownmaxima[i].setPos(tmppos);
	  knownmaxima[i].setFitness(knownmaxima[i].getFitness()*momentumonpeaks[i][dimensions] + (1-momentumonpeaks[i][dimensions])*initialpeaks[i][dimensions].evaluate(curparams));
	  peakwidths[i] = peakwidths[i]*momentumonpeaks[i][dimensions+1] + (1 - momentumonpeaks[i][dimensions+1])*initialpeaks[i][dimensions+1].evaluate(curparams);
	  //	  System.out.println("Updated: "+i+" "+RKUStringUtils.arrayToString(tmppos)+" h="+knownmaxima[i].getFitness());
	};
	updated = true;

	curiteration_cnt = 0;
	staticperiod_cnt++;

      }
      else {
	curiteration_cnt++;
      }

      curiteration++;
      return updated;
    }

  /* Set the settings for this dynamic problem. Since dynamic problems are
     very different in nature is it possible to set each problems parameters
     via this call.<br>
     <b>Note:</b> You must call this method before calling any other methods
     from this class. It will call resetProblem() from the superclass.
     @param settings A hashtable containing the settings for this problem.
  */
  public void setSettings(Hashtable settings, NumericalFramework framework)
    {
      String curstr;
      String[][] tmpmatrix;
      double[][] tmpdoublematrix;
      double[] ltmppos;

      String missingparams = "";
      int i,j;

      initialpeaks = new RKUFunction[0][4];
      momentumonpeaks = new double[0][4];
      knownmaxima = new NumericalOptimum[0];
      peakwidths = new double[0];

      if ((curstr = (String)settings.get("staticperiod")) != null) {
	staticperiodfunction = new RKUFunction(curstr);
	staticperiod = 0;
      }

      if ((curstr = (String)settings.get("problemdimensions")) != null) {
	dimensions = (Integer.valueOf(curstr)).intValue();
	tmppos = new double[dimensions];
      }
      else {
	dimensions = 2;
      };


      // Intervals ***********
      if ((curstr = (String)settings.get("intervals")) != null) {
	tmpdoublematrix = RKUStringUtils.stringToDoubleMatrix(curstr);
	    
	intervals = new Interval[dimensions];
	if (tmpdoublematrix.length!=dimensions) {
	  missingparams += "Wrong number of intervals given in intervals. Expected "+(dimensions)+" intervals.\n";
	  intervals = new Interval[dimensions];
	  for (i=0;i<dimensions;i++) {
	    intervals[i] = new Interval(-10,10);
	  }
	}
	else {

	  for (i=0;i<dimensions;i++) {
	    intervals[i] = new Interval(tmpdoublematrix[i][0],tmpdoublematrix[i][1]);
	  }
	}
      }
      else {
	intervals = new Interval[dimensions];
	for (i=0;i<dimensions;i++) {
	  intervals[i] = new Interval(-10,10);
	}
      }

      // Initial peaks ********
      if ((curstr = (String)settings.get("initialpeaks")) != null) {
	tmpmatrix = RKUStringUtils.stringToStringMatrix(curstr);
	    
	initialpeaks = new RKUFunction[tmpmatrix.length][dimensions+2];
	momentumonpeaks = new double[tmpmatrix.length][dimensions+2];
	knownmaxima = new NumericalOptimum[tmpmatrix.length];
	peakwidths = new double[tmpmatrix.length];
	curparams[0] = 0;
	curparams[1] = 0;
	curparams[2] = 0;
	for (i=0;i<tmpmatrix.length;i++) {
	  if (tmpmatrix[i].length!=dimensions+2) {
	    missingparams += "Wrong number of functions given in intialpeaks at position "+i+" of the array. \nExpected "+(dimensions+2)+", which is position for each dimension, one for the height and \none for the width.\n";
	  }
	  else {
	    for (j=0;j<tmpmatrix[i].length;j++) {
	      initialpeaks[i][j] = new RKUFunction(tmpmatrix[i][j]);
	      momentumonpeaks[i][j] = 0;
	    }
	    ltmppos = new double[dimensions];
	    for (j=0;j<dimensions;j++) {
	      ltmppos[j] = RKUMath.bringInClosedRange(initialpeaks[i][j].evaluate(curparams), intervals[j].min, intervals[j].max);
	    }
	    knownmaxima[i] = new NumericalOptimum(ltmppos, initialpeaks[i][dimensions].evaluate(), true, true, i);
	    peakwidths[i] = initialpeaks[i][dimensions+1].evaluate(curparams);
	  }
	}
      }
      else {
	missingparams += "initialpeaks must be given.\n";
      }

      // Momentum for peaks *********
      if ((curstr = (String)settings.get("momentumonpeaks")) != null) {
	tmpdoublematrix = RKUStringUtils.stringToDoubleMatrix(curstr);
	momentumonpeaks = new double[tmpdoublematrix.length][dimensions+2];
	for (i=0;i<tmpdoublematrix.length;i++) {
	  if (tmpdoublematrix[i].length!=dimensions+2) {
	    missingparams += "Wrong number of functions given in momentumonpeaks at position "+i+" of the array. \nExpected "+(dimensions+2)+", which is momentum on position for each dimension, one for the \nheight and one for the width.\n";
	  }
	  else {
	    for (j=0;j<dimensions+2;j++) {
	      momentumonpeaks[i][j] = tmpdoublematrix[i][j];
	    }
	  }
	}
      }
      if (initialpeaks.length!=momentumonpeaks.length) {
	missingparams += "The number of momentumvectors must be equal to the number of initial peaks.\n";
      }

      if (missingparams.length()>0) {
	System.out.println("--- Initialization of DynamicDeJongTCG1 failed because of the following \nerrors in the settings.\n"+missingparams);
	System.exit(0);
      }

      if (framework!=null) {
	framework.intervals = intervals;
      }

    }

  /** Display the default settings this problem supports. The settings will be 
      returned in the BatchJobScheduler compatible inputformat.<br>
      <b>Note:</b> This method must be overridden in your subclass.
      @return A string representing the possible settings. 
      @see EA.BatchJobScheduler
  */
  public String getDefaultSettings()
    {
      StringBuffer res = new StringBuffer();

      res.append("# ************* Algorithmic settings ********************\n");
      res.append("# Static period is the number of iterations where the problem is static,\n");
      res.append("# i.e. not changed. A RKUFunction can be used to generate\n");
      res.append("# varying periods, e.g. rnd(10;15)\n");
      res.append("staticperiod = 10\n\n");

      res.append("# Initial peaks. (Required)\n");
      res.append("# Each peak consist of four functions. (See RKUjava.math.RKUFunction)\n");
      res.append("# 1. X-coordinate.\n");
      res.append("# 2. Y-coordinate.\n");
      res.append("# 3. Height of the peak.\n");
      res.append("# 4. Width of the peak.\n");
      res.append("# Example: {{2,1,rnd(0;2),1.2+sin(x0)}, {cos((2*pi*x0)/10), sin((2*pi*x0)/10), 1,1}}\n");
      res.append("# The example will create a problem with two peaks where peak 0 located \n");
      res.append("# at (2,1), a random height between 0 and 2 and a width between 0.2 and 2.2.\n");
      res.append("# The second peak is moving on a circle around (0,0) and it has a rotation time\n");
      res.append("# of 10 iterations.	  \n");
      res.append("#\n");
      res.append("# NB1: The x- and y-coordinate will always be in the interval. If the function\n");
      res.append("# returns a value greater(smaller) than max(min) then max(min) will be used.\n");
      res.append("# NB2: ; must be used instead of , in the function definitions.\n");
      res.append("initialpeaks = {{0,0, 1, 1}}\n");
      res.append("\n");
      res.append("# Momentum effect. (Not required, if supplied it must have one for each peak)\n");
      res.append("# The momentum controls how much influence the last value of the entry \n");
      res.append("# in the vector had. If you for instance set the first momentum to 0.2 then\n");
      res.append("# the value of the x-coordinate will be:\n");
      res.append("# new_x = 0.2*old_x + (1 - 0.2)*x_function_from_initialpeaks\n");
      res.append("# Special values are 0 = no influence from old value, 1 = value never changes. \n");
      res.append("momentumonpeaks = {{0, 0, 0, 0}}\n");
      res.append("\n");
      res.append("# The intervals for the problem. \n");
      res.append("# Exactly two intervals must be supplied\n");
      res.append("intervals = {{-2,2},{-2,2}}\n\n");

      return res.toString();
    }
}
